import { Lyric } from 'src/app/services/data-type/common.type';
import { from, zip, Subject, timer, Observable, Subscription } from 'rxjs';
import { skip, delay } from 'rxjs/internal/operators';

const timeExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/;

export interface BaseLyricLine {
  txt: string;
  txtCn: string;
}

interface LyricLine extends BaseLyricLine {
  time: number;
}

interface Handler extends BaseLyricLine {
  lineNum: number;
}

export class WyLyric {
  private lrc: Lyric;
  private playing = false;
  private curNum: number;
  private startStamp: number;
  private pauseStamp: number;
  private timer$: Subscription;

  lines: LyricLine[] = [];
  handler = new Subject<Handler>();

  constructor(lrc: Lyric) {
    this.lrc = lrc;
    this.init();
  }

  private init() {
    if (this.lrc.tlyric) {
      this.genertLyric();
    } else {
      this.generLyric();
    }
  }

  // 解析中文歌词
  private generLyric() {
    const lines = this.lrc.lyric.split('\n');
    lines.forEach(line => this.markLine(line));
  }

  // 解析英文歌词
  private genertLyric() {
    const lines = this.lrc.lyric.split('\n');
    const tlines = this.lrc.tlyric.split('\n').filter(item => timeExp.exec(item));
    // console.log('lines', lines);
    // console.log('tlines', tlines);
    let moreLine = lines.length - tlines.length;
    let tempArr = [];
    tempArr = moreLine > 0 ? [lines, tlines] : [tlines, lines];

    // 找到较少的一项
    const first = timeExp.exec(tempArr[1][0])[0];

    // 将 first 在多的歌词数组中的索引找出来
    const skipIndex = tempArr[0].findIndex(item => {
      const exec = timeExp.exec(item);
      if (exec) {
        return exec[0] === first;
      }
    });
    const _skip = skipIndex === -1 ? 0 : skipIndex;
    const skipItems = tempArr[0].slice(0, _skip);
    if (skipItems.length) {
      // 将有时间戳的多余歌词部分放进歌词里面
      skipItems.forEach(line => this.markLine(line));
    }
    let zipLines$;
    if (moreLine > 0) {
      zipLines$ = zip(from(lines).pipe(skip(_skip)), from(tlines));
    } else {
      zipLines$ = zip(from(lines), from(tlines).pipe(skip(_skip)));
    }
    zipLines$.subscribe(([line, tline]) => this.markLine(line, tline));
  }

  markLine(line: string, tline: string = '') {
    const result = timeExp.exec(line);
    if (result) {
      const txt = line.replace(timeExp, '').trim();
      const txtCn = tline ? tline.replace(timeExp, '').trim() : '';
      if (txt) {
        let thirdResult = result[3] || '00';
        // thirdResult.padEnd(3, '0');
        const _thirdResult = thirdResult.length > 2 ? parseInt(thirdResult) : parseInt(thirdResult) * 10;
        // const _thirdResult = thirdResult.padEnd(3, '0');
        const time = Number(result[1]) * 60 * 1000 + Number(result[2]) * 1000 + _thirdResult;
        this.lines.push({
          txt,
          txtCn,
          time
        });
      }
    }
  }

  play(startTime = 0, skip = false) {
    if (!this.lines.length) return;
    if (!this.playing) {
      this.playing = true;
    }

    this.curNum = this.findCurNum(startTime);
    this.startStamp = Date.now() - startTime;

    if (!skip) {
      this.callHandler(this.curNum - 1);
    }

    if (this.curNum < this.lines.length) {
      this.clearTimer();
      this.playReset();
    }
  }

  private playReset() {
    let line = this.lines[this.curNum];
    const delay = line.time - (Date.now() - this.startStamp);
    this.timer$ = timer(delay).subscribe(res => {
      this.callHandler(this.curNum++);
      if (this.curNum < this.lines.length && this.playing) {
        this.playReset();
      }
    });
  }

  clearTimer() {
    this.timer$ && this.timer$.unsubscribe();
  }

  private callHandler(index: number) {
    if (index > 0) {
      this.handler.next({
        txt: this.lines[index].txt,
        txtCn: this.lines[index].txtCn,
        lineNum: index
      });
    }
  }

  private findCurNum(time: number) {
    const index = this.lines.findIndex(item => time <= item.time);
    return index === -1 ? this.lines.length - 1 : index;
  }

  togglePlay(playing: boolean) {
    const now = Date.now();
    this.playing = playing;
    if (playing) {
      const startTime = (this.pauseStamp || now) - (this.startStamp || now);
      this.play(startTime, true);
    } else {
      this.stop();
      this.pauseStamp = now;
    }
  }

  stop() {
    if (this.playing) {
      this.playing = false;
    }
    this.clearTimer();
  }

  seek(time: number) {
    this.play(time);
  }
}
